package com.culture.bootdemo.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author wen
 * @date 2019/11/20 14:10
 * @Description
 **/
@Component
@Slf4j
public class LockUtil {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    private static final String UNLOCK_LUA;

    /**
     * 释放锁脚本，原子操作
     */
    static {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        stringBuilder.append("then ");
        stringBuilder.append("    return redis.call(\"del\",KEYS[1]) ");
        stringBuilder.append("else ");
        stringBuilder.append("    return 0 ");
        stringBuilder.append("end ");
        UNLOCK_LUA = stringBuilder.toString();
    }


    /**
     * 获取分布式锁，原子操作
     * 此刻尽量大于1s,否则要修改此处代码：Expiration.seconds(timeUnit.toSeconds(expire)
     *
     * @param lockKey
     * @param requestId 唯一ID, 可以使用UUID.randomUUID().toString();
     * @param expire
     * @param timeUnit
     * @return
     */
    public boolean tryLock(String lockKey, String requestId, long expire, TimeUnit timeUnit) {
        try {
            RedisCallback<Boolean> callback = (connection) -> connection.set(lockKey.getBytes(StandardCharsets.UTF_8), requestId.getBytes(StandardCharsets.UTF_8), Expiration.seconds(timeUnit.toSeconds(expire)), RedisStringCommands.SetOption.SET_IF_ABSENT);
            Boolean execute = redisTemplate.execute(callback);
            return null != execute && execute;
        } catch (Exception e) {
            log.error("redis lock error.", e);
        }
        return false;
    }

    /**
     * 释放锁
     *
     * @param lockKey
     * @param requestId 唯一ID
     * @return
     */
    public boolean releaseLock(String lockKey, String requestId) {
        RedisCallback<Boolean> callback = (connection) -> connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN, 1, lockKey.getBytes(StandardCharsets.UTF_8), requestId.getBytes(StandardCharsets.UTF_8));
        Boolean execute = redisTemplate.execute(callback);
        return null != execute && execute;
    }

    /**
     * 获取Redis锁的value值
     *
     * @param lockKey
     * @return
     */
    public String get(String lockKey) {
        try {
            RedisCallback<String> callback = (connection) -> new String(Objects.requireNonNull(connection.get(lockKey.getBytes())), StandardCharsets.UTF_8);
            return redisTemplate.execute(callback);
        } catch (Exception e) {
            log.error("get redis occurred an exception", e);
        }
        return null;
    }

}
